Q1

First I load the data files

library(tidyverse)
dat <- read.csv("/Users/susu/Desktop/Hong\ Kong/Semester2/Big_Data/assignment_data/as1/migration2012.csv")
states <- read.csv("/Users/susu/Desktop/Hong\ Kong/Semester2/Big_Data/assignment_data/as1/states_chord.csv")

dat <- dat %>%
tidyr::gather(key=From, value=value, Alabama,Alaska,Arizona,Arkansas,California,Colorado,Connecticut,Delaware,District.of.Columbia,Florida,Georgia,Hawaii,Idaho,Illinois,Indiana,Iowa,Kansas,Kentucky,Louisiana,Maine,Maryland,Massachusetts,Michigan,Minnesota,Mississippi,Missouri,Montana,Nebraska,Nevada,New.Hampshire,New.Jersey,New.Mexico,New.York,North.Carolina,North.Dakota,Ohio,Oklahoma,Oregon,Pennsylvania,Rhode.Island,South.Carolina,South.Dakota,Tennessee,Texas,Utah,Vermont,Virginia,Washington,West.Virginia,Wisconsin,Wyoming)

Delete the puctuations.

dat <- data.frame(apply(dat, 2, function(y) gsub("[[:punct:]]", " ", y)))
dat

Then I add two new columns to the above data frame.

d1 <- merge(dat, states, by.x="From", by.y="States")
d1 <- d1[,c(-5,-6)]
names(d1)[4] <- "ID_From"
d2 <- merge(d1, states, by.x="To", by.y="States")
d2 <- d2[,c(-6,-7)]
names(d2)[5] <- "ID_To"
dat <- d2
dat

Sort the data frame in the ascending order

dat <- arrange(dat, ID_From)
dat <- arrange(dat, ID_To)
dat

Convert the above data frame into the matrix form

t1 <- dat[,c(1,2,4,5)]
t1 <- t1 %>%
  spread(key=To, value=ID_To)
t2 <- dat[,c(1,2,3,4)]
t2 <- t2 %>%
  spread(key=To, value=value)
t3 <- rbind(t1[1,],t2)
t3$From <- as.character(t3$From)
t3[1,2] <- 0
t3[1,1] <- "ID_To"
t3 <- t3 %>%
  arrange(ID_From)
library(data.table)
data.table 1.10.4
**********
This installation of data.table has not detected OpenMP support. It will still work but in single-threaded mode. If this a Mac and you obtained the Mac binary of data.table from CRAN, CRAN's Mac does not yet support OpenMP. In the meantime please follow our Mac installation instructions on the data.table homepage. If it works and you observe benefits from multiple threads as others have reported, please convince Simon Ubanek by sending him evidence and ask him to turn on OpenMP support when CRAN builds package binaries for Mac. Alternatives are to install Ubuntu on your Mac (which I have done and works well) or use Windows where OpenMP is supported and works well.
**********
  The fastest way to learn (by data.table authors): https://www.datacamp.com/courses/data-analysis-the-data-table-way
  Documentation: ?data.table, example(data.table) and browseVignettes("data.table")
  Release notes, videos and slides: http://r-datatable.com
-----------------------------------------------------------------------------------------------------------------------------------
data.table + dplyr code now lives in dtplyr.
Please library(dtplyr)!
-----------------------------------------------------------------------------------------------------------------------------------

 次のパッケージを付け加えます: ‘data.table’ 

 以下のオブジェクトは ‘package:dplyr’ からマスクされています: 

     between, first, last 

 以下のオブジェクトは ‘package:purrr’ からマスクされています: 

     transpose 
setcolorder(t3,c("From","ID_From","Connecticut","Maine","Massachusetts","New Hampshire","Rhode Island",
"Vermont","New Jersey","New York","Pennsylvania","Illinois","Indiana",
"Michigan","Ohio","Wisconsin","Iowa","Kansas","Minnesota",
"Missouri","Nebraska","North Dakota","South Dakota","Delaware","Florida",
"Georgia","Maryland","North Carolina","South Carolina","Virginia","District of Columbia",
"West Virginia","Alabama","Kentucky","Mississippi","Tennessee","Arkansas",
"Louisiana","Oklahoma","Texas","Arizona","Colorado","Idaho",
"Montana","Nevada","New Mexico","Utah","Wyoming","Alaska",
"California","Hawaii","Oregon","Washington"))
t3 <- t3[c(-1),c(-2)]
t4 <- t3[,-1]
rownames(t4) <- t3[,1]
t5 <- data.frame(apply(t4, 2, function(y) as.numeric(y)))
rownames(t5) <- t3[,1]
t5

Drawing the chord diagram

chorddiag(as.matrix(t5),groupColors=states$Color,showTicks=F,groupnamePadding = 20,groupThickness=.05,groupnameFontsize=10)
row names of the 'data' matrix differ from its column names or the 'groupNames' argument.

Q2

Read “Stops On Lines” and the all GIS data of bus lines.

library(dplyr)
library(sp)
library(rgdal)
library(leaflet)
library(ggmap)

# Bus Stops
SOL <- readOGR("/Users/susu/Desktop/Hong\ Kong/Semester2/Big_Data/assignment_data/as1/BusStops1216","StopsOnLines1216")
SOL.pj <- spTransform(SOL, CRS("+proj=longlat +datum=WGS84"))

# Bus Routes
CC <- readOGR("/Users/susu/Desktop/Hong\ Kong/Semester2/Big_Data/assignment_data/as1/ComCir1216","ComCir1216")
CC.pj <- spTransform(CC, CRS("+proj=longlat +datum=WGS84"))
LE <- readOGR("/Users/susu/Desktop/Hong\ Kong/Semester2/Big_Data/assignment_data/as1/LimExp1216","LimExp1216")
LE.pj <- spTransform(LE, CRS("+proj=longlat +datum=WGS84"))
LCBD <- readOGR("/Users/susu/Desktop/Hong\ Kong/Semester2/Big_Data/assignment_data/as1/LocalCBD1216","LocalCBD1216")
LCBD.pj <- spTransform(LCBD, CRS("+proj=longlat +datum=WGS84"))
LNCBD <- readOGR("/Users/susu/Desktop/Hong\ Kong/Semester2/Big_Data/assignment_data/as1/LocalNonCBD1216","LocalNonCBD1216")
LNCBD.pj <- spTransform(LNCBD, CRS("+proj=longlat +datum=WGS84"))
RBRT <- readOGR("/Users/susu/Desktop/Hong\ Kong/Semester2/Big_Data/assignment_data/as1/RapidBRT1216","RapidBRT1216")
RBRT.pj <- spTransform(RBRT, CRS("+proj=longlat +datum=WGS84"))

tmp_CC <- geometry(CC.pj)
tmp_LE <- geometry(LE.pj)
tmp_LCBD <- geometry(LCBD.pj)
tmp_LNCBD <- geometry(LNCBD.pj)
tmp_RBRT <- geometry(RBRT.pj)

# Individual Bus Route
pjs <- list()
tmps <- list()
layer_list <- ogrListLayers("/Users/susu/Desktop/Hong\ Kong/Semester2/Big_Data/assignment_data/as1/Individuals1216")
for (i in layer_list){
  if (i != 728){
    a <- spTransform(readOGR("/Users/susu/Desktop/Hong\ Kong/Semester2/Big_Data/assignment_data/as1/Individuals1216", toString(i)), CRS("+proj=longlat +datum=WGS84"))
    pjs <- c(pjs, a)
    tmps <- c(tmps, geometry(a))
  }
}

make Line_list

tmps <- c(tmps, tmp_CC, tmp_LE, tmp_LCBD, tmp_LNCBD, tmp_RBRT)
Line_list <- list()
for (i in 1:length(tmps)){
  for (j in 1:length(tmps[[i]])){
    Line_list <- c(Line_list, tmps[[i]][j]@lines[[1]]@Lines)
  }
}

make new_id

pjs <- c(pjs, CC.pj, LE.pj, LCBD.pj, LNCBD.pj, RBRT.pj)
LinLSs <- list()
for (i in 1:length(pjs)){
 LinLSs <- c(LinLSs, sapply(pjs[[i]]@lines, function(x) length(x@Lines)))
}
LinLSs <- LinLSs %>% unlist()
new_id <- sapply(1:length(LinLSs), function(x) paste0(x, "_", seq.int(LinLSs[[x]]))) %>% 
  unlist()

make data frame

## make a new data.frame (only route_id)
DAT=data.frame(matrix(rep(NA,1),nrow=1))[-1,]
for (i in 1:length(pjs)){
  df <- data.frame(route_id = pjs[[i]]@data$VAR_IDENT)
  DAT <- rbind(DAT, df)
}
rownames(DAT) <- new_id
SLDF <- mapply(function(x, y) Lines(x, ID = y), x = Line_list, y = new_id) %>%
  #list() %>%
  SpatialLines() %>% 
  SpatialLinesDataFrame(data = DAT)

make new lines and LA map

dat <- geocode('Los Angels')
Information from URL : http://maps.googleapis.com/maps/api/geocode/json?address=Los%20Angels&sensor=false
leaflet() %>%
  setView(lng = dat['lon'], lat = dat['lat']    , zoom = 11) %>%
  addPolylines(data = SLDF, color = "black", opacity = 1, weight = 1) %>% 
  addCircles(data=SOL.pj@data,~LONG, ~LAT, color = "red", weight = 0.3) %>%
  addTiles()

Q3

First I load the data.

library(quantmod)
library(highcharter)

x <- getSymbols("AUD/JPY", src = "oanda", auto.assign = FALSE)
y <- getSymbols("GBP/USD", src = "oanda", auto.assign = FALSE)

Next make Bollinger’s bands for each exchange rate.

x.BBands.ll <- BBands(x)$dn
x.BBands.ul <- BBands(x)$up
x.BBands.m <- BBands(x)$mavg
y.BBands.ll <- BBands(y)$dn
y.BBands.ul <- BBands(y)$up
y.BBands.m <- BBands(y)$mavg

The drawing code is as follows.

hc <- highchart(type="stock") %>% 
  hc_title(text="Charting Exchange Rates") %>% 
  hc_subtitle(text = "Data extracted using quantmod package") %>% 
  hc_yAxis_multiples(
    list(top = "0%", height = "50%", offset=0, opposite=TRUE),
    list(top = "50%", height = "50%", offset=0, opposite=TRUE)
  )%>%
  hc_add_series(x, id = "audjpy",name ="audjpy", yAxis=0, color="blue", lineWidth=1.5) %>%
  hc_add_series(x.BBands.ll, id = "audjpy.ll", name="audjpy Lower BBands",yAxis=0,
                color="black",dashStyle='shortdash', lineWidth=1) %>%
  hc_add_series(x.BBands.ul, id = "audjpy.ul", name="audjpy Upper BBands",yAxis=0,
                color="black",lineWidth=1) %>%
  hc_add_series(x.BBands.m, id = "audjpy.m",name="audjpy BBands MA", yAxis=0,
                color="red",lineWidth=1) %>%
  hc_add_series(y, id = "gbpusd",name="gbpusd",yAxis=1, color="green", lineWidth=1.5) %>%
  hc_add_series(y.BBands.ll, id = "gbpusd.ll",name="gbpusd Lower BBands", yAxis=1,
                color="black",dashStyle='shortdash',lineWidth=1) %>%
  hc_add_series(y.BBands.ul, id = "gbpusd.ul",name="gbpusd Upper BBands", yAxis=1,
                color="black",lineWidth=1) %>%
  hc_add_series(y.BBands.m, id = "gbpusd.m",name="gbpusd BBands MA", yAxis=1,
                color="red",lineWidth=1) %>%
  hc_add_theme(hc_theme_538())
hc

Q4

Load libraries and check the raw data. And make ffdf after converting character columns to factor columns in original df.

library(nycflights13)
library(ffbase)
library(ffbase2)
library(biglm)
library(pROC)
library(chron)

tmp <- flights
tmp$carrier <- as.factor(tmp$carrier)
tmp$tailnum <- as.factor(tmp$tailnum)
tmp$origin <- as.factor(tmp$origin)
tmp$dest <- as.factor(tmp$dest)

flightff <- as.ffdf(tmp)

Next I make new columns as follows

flightff$Delay <- ffifelse(flightff$dep_delay > 0 | flightff$dep_delay == 0 , 1,0)
flightff$DepHour <- flightff$hour
flightff$Car <- ffifelse(flightff$carrier %in% as.factor(c("DL","US","DH","UA")), 1, 0)
flightff$Night <- ffifelse(flightff$hour > 18 | flightff$hour < 6, 1, 0)
flightff$Weekend <- ffifelse(day.of.week(month=flightff$month, day=flightff$day, year=flightff$year) == 6, 1, 0)

I exclude the rows whose Delay values are NA and rename it to logitff. And then I split the dataset into train set and test set.

logitff <- flightff[!is.na(flightff$Delay),]
indx <- ff(1:nrow(logitff))
p <- 0.7
trainIndx <- ff(indx[1:trunc(length(indx)*p)])
trainset <- logitff[trainIndx,]
testIndx <- ff(indx[(trunc(length(indx)*p)+1):length(indx)])
testset <- logitff[testIndx,]

Logistic regression

fit <- bigglm.ffdf(Delay~DepHour+Car+Night+Weekend, data = trainset, family=binomial(), sandwich=TRUE)
summary(fit)
Large data regression model: bigglm(Delay ~ DepHour + Car + Night + Weekend, data = trainset, 
    family = binomial(), sandwich = TRUE)
Sample size =  229964 
               Coef    (95%     CI)     SE p
(Intercept) -1.5418 -1.5731 -1.5104 0.0157 0
DepHour      0.1020  0.0996  0.1044 0.0012 0
Car         -0.0680 -0.0858 -0.0503 0.0089 0
Night       -0.2619 -0.2912 -0.2326 0.0147 0
Weekend     -0.1543 -0.1821 -0.1265 0.0139 0
Sandwich (model-robust) standard errors

predict and make confusionmatrix in train_set

train_pred <- predict(fit, newdata = trainset, type="response")
train_pred <- ifelse(train_pred>0.5, 1,0)
train_confusion <- table(as.integer(as.data.frame(trainset)$Delay), as.integer(train_pred))
train_confusion <- addmargins(train_confusion)
train_confusion
     
           0      1    Sum
  0    94128  36392 130520
  1    56845  42599  99444
  Sum 150973  78991 229964

predict and make confusionmatrix in test_set

test_pred <- predict(fit, newdata = testset, type="response")
test_pred <- ifelse(test_pred>0.5, 1,0)
test_confusion <- table(as.integer(as.data.frame(testset)$Delay), as.integer(test_pred))
test_confusion <- addmargins(test_confusion)
test_confusion
     
          0     1   Sum
  0   39997 13058 53055
  1   25558 19944 45502
  Sum 65555 33002 98557

Draw ROC curve

test_pred <- predict(fit, newdata = testset, type="response")
roc <- roc(as.integer(as.data.frame(testset)$Delay), as.numeric(test_pred))
plot(roc)

Q5

First I load the data. And before using spark I delete the irrelevant columns.

Remove the observations satisfying the condition

Split this data into trainset and testset.

test
$test
NA

Use Decision tree

decision_tree <- train %>%
  ml_decision_tree(response="BOROUGH", features = c("LATITUDE","LONGITUDE"), max.bins = 200L, max.depth = 10L, seed=123L) %>%

Prediction

table(pred$BOROUGH, pred$prediction)
               
                    0     1     2     3     4
  BRONX             0     0     0  8876     0
  BROOKLYN      21181     0    24     0     1
  MANHATTAN         0 17944     0     0     0
  QUEENS           25     1 17777     0     0
  STATEN ISLAND    23     0     0     0  3169
LS0tCnRpdGxlOiAiQmlnIERhdGEgQW5hbHl0aWNzIEFzc2lnbm1lbnQgMSIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQKICBodG1sX2RvY3VtZW50OiBkZWZhdWx0CiAgcGRmX2RvY3VtZW50OiBkZWZhdWx0Ci0tLQoKIyMgUTEKCkZpcnN0IEkgbG9hZCB0aGUgZGF0YSBmaWxlcwpgYGB7cn0KbGlicmFyeSh0aWR5dmVyc2UpCmRhdCA8LSByZWFkLmNzdigiL1VzZXJzL3N1c3UvRGVza3RvcC9Ib25nXCBLb25nL1NlbWVzdGVyMi9CaWdfRGF0YS9hc3NpZ25tZW50X2RhdGEvYXMxL21pZ3JhdGlvbjIwMTIuY3N2IikKc3RhdGVzIDwtIHJlYWQuY3N2KCIvVXNlcnMvc3VzdS9EZXNrdG9wL0hvbmdcIEtvbmcvU2VtZXN0ZXIyL0JpZ19EYXRhL2Fzc2lnbm1lbnRfZGF0YS9hczEvc3RhdGVzX2Nob3JkLmNzdiIpCgpkYXQgPC0gZGF0ICU+JQp0aWR5cjo6Z2F0aGVyKGtleT1Gcm9tLCB2YWx1ZT12YWx1ZSwgQWxhYmFtYSxBbGFza2EsQXJpem9uYSxBcmthbnNhcyxDYWxpZm9ybmlhLENvbG9yYWRvLENvbm5lY3RpY3V0LERlbGF3YXJlLERpc3RyaWN0Lm9mLkNvbHVtYmlhLEZsb3JpZGEsR2VvcmdpYSxIYXdhaWksSWRhaG8sSWxsaW5vaXMsSW5kaWFuYSxJb3dhLEthbnNhcyxLZW50dWNreSxMb3Vpc2lhbmEsTWFpbmUsTWFyeWxhbmQsTWFzc2FjaHVzZXR0cyxNaWNoaWdhbixNaW5uZXNvdGEsTWlzc2lzc2lwcGksTWlzc291cmksTW9udGFuYSxOZWJyYXNrYSxOZXZhZGEsTmV3LkhhbXBzaGlyZSxOZXcuSmVyc2V5LE5ldy5NZXhpY28sTmV3LllvcmssTm9ydGguQ2Fyb2xpbmEsTm9ydGguRGFrb3RhLE9oaW8sT2tsYWhvbWEsT3JlZ29uLFBlbm5zeWx2YW5pYSxSaG9kZS5Jc2xhbmQsU291dGguQ2Fyb2xpbmEsU291dGguRGFrb3RhLFRlbm5lc3NlZSxUZXhhcyxVdGFoLFZlcm1vbnQsVmlyZ2luaWEsV2FzaGluZ3RvbixXZXN0LlZpcmdpbmlhLFdpc2NvbnNpbixXeW9taW5nKQpgYGAKCkRlbGV0ZSB0aGUgcHVjdHVhdGlvbnMuCmBgYHtyfQpkYXQgPC0gZGF0YS5mcmFtZShhcHBseShkYXQsIDIsIGZ1bmN0aW9uKHkpIGdzdWIoIltbOnB1bmN0Ol1dIiwgIiAiLCB5KSkpCmRhdApgYGAKClRoZW4gSSBhZGQgdHdvIG5ldyBjb2x1bW5zIHRvIHRoZSBhYm92ZSBkYXRhIGZyYW1lLgoKYGBge3J9CmQxIDwtIG1lcmdlKGRhdCwgc3RhdGVzLCBieS54PSJGcm9tIiwgYnkueT0iU3RhdGVzIikKZDEgPC0gZDFbLGMoLTUsLTYpXQpuYW1lcyhkMSlbNF0gPC0gIklEX0Zyb20iCgpkMiA8LSBtZXJnZShkMSwgc3RhdGVzLCBieS54PSJUbyIsIGJ5Lnk9IlN0YXRlcyIpCmQyIDwtIGQyWyxjKC02LC03KV0KbmFtZXMoZDIpWzVdIDwtICJJRF9UbyIKZGF0IDwtIGQyCmRhdApgYGAKClNvcnQgdGhlIGRhdGEgZnJhbWUgaW4gdGhlIGFzY2VuZGluZyBvcmRlcgoKYGBge3J9CmRhdCA8LSBhcnJhbmdlKGRhdCwgSURfRnJvbSkKZGF0IDwtIGFycmFuZ2UoZGF0LCBJRF9UbykKZGF0CmBgYAoKQ29udmVydCB0aGUgYWJvdmUgZGF0YSBmcmFtZSBpbnRvIHRoZSBtYXRyaXggZm9ybQoKYGBge3J9CnQxIDwtIGRhdFssYygxLDIsNCw1KV0KdDEgPC0gdDEgJT4lCiAgc3ByZWFkKGtleT1UbywgdmFsdWU9SURfVG8pCgp0MiA8LSBkYXRbLGMoMSwyLDMsNCldCnQyIDwtIHQyICU+JQogIHNwcmVhZChrZXk9VG8sIHZhbHVlPXZhbHVlKQoKdDMgPC0gcmJpbmQodDFbMSxdLHQyKQp0MyRGcm9tIDwtIGFzLmNoYXJhY3Rlcih0MyRGcm9tKQp0M1sxLDJdIDwtIDAKdDNbMSwxXSA8LSAiSURfVG8iCnQzIDwtIHQzICU+JQogIGFycmFuZ2UoSURfRnJvbSkKCmxpYnJhcnkoZGF0YS50YWJsZSkKc2V0Y29sb3JkZXIodDMsYygiRnJvbSIsIklEX0Zyb20iLCJDb25uZWN0aWN1dCIsIk1haW5lIiwiTWFzc2FjaHVzZXR0cyIsIk5ldyBIYW1wc2hpcmUiLCJSaG9kZSBJc2xhbmQiLAoiVmVybW9udCIsIk5ldyBKZXJzZXkiLCJOZXcgWW9yayIsIlBlbm5zeWx2YW5pYSIsIklsbGlub2lzIiwiSW5kaWFuYSIsCiJNaWNoaWdhbiIsIk9oaW8iLCJXaXNjb25zaW4iLCJJb3dhIiwiS2Fuc2FzIiwiTWlubmVzb3RhIiwKIk1pc3NvdXJpIiwiTmVicmFza2EiLCJOb3J0aCBEYWtvdGEiLCJTb3V0aCBEYWtvdGEiLCJEZWxhd2FyZSIsIkZsb3JpZGEiLAoiR2VvcmdpYSIsIk1hcnlsYW5kIiwiTm9ydGggQ2Fyb2xpbmEiLCJTb3V0aCBDYXJvbGluYSIsIlZpcmdpbmlhIiwiRGlzdHJpY3Qgb2YgQ29sdW1iaWEiLAoiV2VzdCBWaXJnaW5pYSIsIkFsYWJhbWEiLCJLZW50dWNreSIsIk1pc3Npc3NpcHBpIiwiVGVubmVzc2VlIiwiQXJrYW5zYXMiLAoiTG91aXNpYW5hIiwiT2tsYWhvbWEiLCJUZXhhcyIsIkFyaXpvbmEiLCJDb2xvcmFkbyIsIklkYWhvIiwKIk1vbnRhbmEiLCJOZXZhZGEiLCJOZXcgTWV4aWNvIiwiVXRhaCIsIld5b21pbmciLCJBbGFza2EiLAoiQ2FsaWZvcm5pYSIsIkhhd2FpaSIsIk9yZWdvbiIsIldhc2hpbmd0b24iKSkKCnQzIDwtIHQzW2MoLTEpLGMoLTIpXQoKdDQgPC0gdDNbLC0xXQpyb3duYW1lcyh0NCkgPC0gdDNbLDFdCgp0NSA8LSBkYXRhLmZyYW1lKGFwcGx5KHQ0LCAyLCBmdW5jdGlvbih5KSBhcy5udW1lcmljKHkpKSkKcm93bmFtZXModDUpIDwtIHQzWywxXQp0NQpgYGAKCkRyYXdpbmcgdGhlIGNob3JkIGRpYWdyYW0KYGBge3J9CmxpYnJhcnkoY2hvcmRkaWFnKQoKY2hvcmRkaWFnKGFzLm1hdHJpeCh0NSksZ3JvdXBDb2xvcnM9c3RhdGVzJENvbG9yLHNob3dUaWNrcz1GLGdyb3VwbmFtZVBhZGRpbmc9MjAsZ3JvdXBUaGlja25lc3M9LjA1LGdyb3VwbmFtZUZvbnRzaXplPTEwKQpgYGAKCgojIyBRMgoKUmVhZCAiU3RvcHMgT24gTGluZXMiIGFuZCB0aGUgYWxsIEdJUyBkYXRhIG9mIGJ1cyBsaW5lcy4KYGBge3J9CmxpYnJhcnkoZHBseXIpCmxpYnJhcnkoc3ApCmxpYnJhcnkocmdkYWwpCmxpYnJhcnkobGVhZmxldCkKbGlicmFyeShnZ21hcCkKCiMgQnVzIFN0b3BzClNPTCA8LSByZWFkT0dSKCIvVXNlcnMvc3VzdS9EZXNrdG9wL0hvbmdcIEtvbmcvU2VtZXN0ZXIyL0JpZ19EYXRhL2Fzc2lnbm1lbnRfZGF0YS9hczEvQnVzU3RvcHMxMjE2IiwiU3RvcHNPbkxpbmVzMTIxNiIpClNPTC5waiA8LSBzcFRyYW5zZm9ybShTT0wsIENSUygiK3Byb2o9bG9uZ2xhdCArZGF0dW09V0dTODQiKSkKCiMgQnVzIFJvdXRlcwpDQyA8LSByZWFkT0dSKCIvVXNlcnMvc3VzdS9EZXNrdG9wL0hvbmdcIEtvbmcvU2VtZXN0ZXIyL0JpZ19EYXRhL2Fzc2lnbm1lbnRfZGF0YS9hczEvQ29tQ2lyMTIxNiIsIkNvbUNpcjEyMTYiKQpDQy5waiA8LSBzcFRyYW5zZm9ybShDQywgQ1JTKCIrcHJvaj1sb25nbGF0ICtkYXR1bT1XR1M4NCIpKQpMRSA8LSByZWFkT0dSKCIvVXNlcnMvc3VzdS9EZXNrdG9wL0hvbmdcIEtvbmcvU2VtZXN0ZXIyL0JpZ19EYXRhL2Fzc2lnbm1lbnRfZGF0YS9hczEvTGltRXhwMTIxNiIsIkxpbUV4cDEyMTYiKQpMRS5waiA8LSBzcFRyYW5zZm9ybShMRSwgQ1JTKCIrcHJvaj1sb25nbGF0ICtkYXR1bT1XR1M4NCIpKQpMQ0JEIDwtIHJlYWRPR1IoIi9Vc2Vycy9zdXN1L0Rlc2t0b3AvSG9uZ1wgS29uZy9TZW1lc3RlcjIvQmlnX0RhdGEvYXNzaWdubWVudF9kYXRhL2FzMS9Mb2NhbENCRDEyMTYiLCJMb2NhbENCRDEyMTYiKQpMQ0JELnBqIDwtIHNwVHJhbnNmb3JtKExDQkQsIENSUygiK3Byb2o9bG9uZ2xhdCArZGF0dW09V0dTODQiKSkKTE5DQkQgPC0gcmVhZE9HUigiL1VzZXJzL3N1c3UvRGVza3RvcC9Ib25nXCBLb25nL1NlbWVzdGVyMi9CaWdfRGF0YS9hc3NpZ25tZW50X2RhdGEvYXMxL0xvY2FsTm9uQ0JEMTIxNiIsIkxvY2FsTm9uQ0JEMTIxNiIpCkxOQ0JELnBqIDwtIHNwVHJhbnNmb3JtKExOQ0JELCBDUlMoIitwcm9qPWxvbmdsYXQgK2RhdHVtPVdHUzg0IikpClJCUlQgPC0gcmVhZE9HUigiL1VzZXJzL3N1c3UvRGVza3RvcC9Ib25nXCBLb25nL1NlbWVzdGVyMi9CaWdfRGF0YS9hc3NpZ25tZW50X2RhdGEvYXMxL1JhcGlkQlJUMTIxNiIsIlJhcGlkQlJUMTIxNiIpClJCUlQucGogPC0gc3BUcmFuc2Zvcm0oUkJSVCwgQ1JTKCIrcHJvaj1sb25nbGF0ICtkYXR1bT1XR1M4NCIpKQoKdG1wX0NDIDwtIGdlb21ldHJ5KENDLnBqKQp0bXBfTEUgPC0gZ2VvbWV0cnkoTEUucGopCnRtcF9MQ0JEIDwtIGdlb21ldHJ5KExDQkQucGopCnRtcF9MTkNCRCA8LSBnZW9tZXRyeShMTkNCRC5waikKdG1wX1JCUlQgPC0gZ2VvbWV0cnkoUkJSVC5waikKCiMgSW5kaXZpZHVhbCBCdXMgUm91dGUKcGpzIDwtIGxpc3QoKQp0bXBzIDwtIGxpc3QoKQpsYXllcl9saXN0IDwtIG9nckxpc3RMYXllcnMoIi9Vc2Vycy9zdXN1L0Rlc2t0b3AvSG9uZ1wgS29uZy9TZW1lc3RlcjIvQmlnX0RhdGEvYXNzaWdubWVudF9kYXRhL2FzMS9JbmRpdmlkdWFsczEyMTYiKQpmb3IgKGkgaW4gbGF5ZXJfbGlzdCl7CiAgaWYgKGkgIT0gNzI4KXsKICAgIGEgPC0gc3BUcmFuc2Zvcm0ocmVhZE9HUigiL1VzZXJzL3N1c3UvRGVza3RvcC9Ib25nXCBLb25nL1NlbWVzdGVyMi9CaWdfRGF0YS9hc3NpZ25tZW50X2RhdGEvYXMxL0luZGl2aWR1YWxzMTIxNiIsIHRvU3RyaW5nKGkpKSwgQ1JTKCIrcHJvaj1sb25nbGF0ICtkYXR1bT1XR1M4NCIpKQogICAgcGpzIDwtIGMocGpzLCBhKQogICAgdG1wcyA8LSBjKHRtcHMsIGdlb21ldHJ5KGEpKQogIH0KfQpgYGAKCm1ha2UgTGluZV9saXN0CmBgYHtyfQp0bXBzIDwtIGModG1wcywgdG1wX0NDLCB0bXBfTEUsIHRtcF9MQ0JELCB0bXBfTE5DQkQsIHRtcF9SQlJUKQoKTGluZV9saXN0IDwtIGxpc3QoKQpmb3IgKGkgaW4gMTpsZW5ndGgodG1wcykpewogIGZvciAoaiBpbiAxOmxlbmd0aCh0bXBzW1tpXV0pKXsKICAgIExpbmVfbGlzdCA8LSBjKExpbmVfbGlzdCwgdG1wc1tbaV1dW2pdQGxpbmVzW1sxXV1ATGluZXMpCiAgfQp9CmBgYAoKCm1ha2UgbmV3X2lkCmBgYHtyfQpwanMgPC0gYyhwanMsIENDLnBqLCBMRS5waiwgTENCRC5waiwgTE5DQkQucGosIFJCUlQucGopCkxpbkxTcyA8LSBsaXN0KCkKZm9yIChpIGluIDE6bGVuZ3RoKHBqcykpewogTGluTFNzIDwtIGMoTGluTFNzLCBzYXBwbHkocGpzW1tpXV1AbGluZXMsIGZ1bmN0aW9uKHgpIGxlbmd0aCh4QExpbmVzKSkpCn0KTGluTFNzIDwtIExpbkxTcyAlPiUgdW5saXN0KCkKCm5ld19pZCA8LSBzYXBwbHkoMTpsZW5ndGgoTGluTFNzKSwgZnVuY3Rpb24oeCkgcGFzdGUwKHgsICJfIiwgc2VxLmludChMaW5MU3NbW3hdXSkpKSAlPiUgCiAgdW5saXN0KCkKYGBgCgptYWtlIGRhdGEgZnJhbWUKYGBge3J9CiMjIG1ha2UgYSBuZXcgZGF0YS5mcmFtZSAob25seSByb3V0ZV9pZCkKREFUPWRhdGEuZnJhbWUobWF0cml4KHJlcChOQSwxKSxucm93PTEpKVstMSxdCmZvciAoaSBpbiAxOmxlbmd0aChwanMpKXsKICBkZiA8LSBkYXRhLmZyYW1lKHJvdXRlX2lkID0gcGpzW1tpXV1AZGF0YSRWQVJfSURFTlQpCiAgREFUIDwtIHJiaW5kKERBVCwgZGYpCn0Kcm93bmFtZXMoREFUKSA8LSBuZXdfaWQKClNMREYgPC0gbWFwcGx5KGZ1bmN0aW9uKHgsIHkpIExpbmVzKHgsIElEID0geSksIHggPSBMaW5lX2xpc3QsIHkgPSBuZXdfaWQpICU+JQogICNsaXN0KCkgJT4lCiAgU3BhdGlhbExpbmVzKCkgJT4lIAogIFNwYXRpYWxMaW5lc0RhdGFGcmFtZShkYXRhID0gREFUKQpgYGAKCgptYWtlIG5ldyBsaW5lcyBhbmQgTEEgbWFwCmBgYHtyfQpkYXQgPC0gZ2VvY29kZSgnTG9zIEFuZ2VscycpCgpsZWFmbGV0KCkgJT4lCiAgc2V0VmlldyhsbmcgPSBkYXRbJ2xvbiddLCBsYXQgPSBkYXRbJ2xhdCddCSwgem9vbSA9IDExKSAlPiUKICBhZGRQb2x5bGluZXMoZGF0YSA9IFNMREYsIGNvbG9yID0gImJsYWNrIiwgb3BhY2l0eSA9IDEsIHdlaWdodCA9IDEpICU+JSAKICBhZGRDaXJjbGVzKGRhdGE9U09MLnBqQGRhdGEsfkxPTkcsIH5MQVQsIGNvbG9yID0gInJlZCIsIHdlaWdodCA9IDAuMykgJT4lCiAgYWRkVGlsZXMoKQpgYGAKCgojIyBRMwoKRmlyc3QgSSBsb2FkIHRoZSBkYXRhLgpgYGB7cn0KbGlicmFyeShxdWFudG1vZCkKbGlicmFyeShoaWdoY2hhcnRlcikKCnggPC0gZ2V0U3ltYm9scygiQVVEL0pQWSIsIHNyYyA9ICJvYW5kYSIsIGF1dG8uYXNzaWduID0gRkFMU0UpCnkgPC0gZ2V0U3ltYm9scygiR0JQL1VTRCIsIHNyYyA9ICJvYW5kYSIsIGF1dG8uYXNzaWduID0gRkFMU0UpCmBgYAoKTmV4dCBtYWtlIEJvbGxpbmdlcidzIGJhbmRzIGZvciBlYWNoIGV4Y2hhbmdlIHJhdGUuCmBgYHtyfQp4LkJCYW5kcy5sbCA8LSBCQmFuZHMoeCkkZG4KeC5CQmFuZHMudWwgPC0gQkJhbmRzKHgpJHVwCnguQkJhbmRzLm0gPC0gQkJhbmRzKHgpJG1hdmcKeS5CQmFuZHMubGwgPC0gQkJhbmRzKHkpJGRuCnkuQkJhbmRzLnVsIDwtIEJCYW5kcyh5KSR1cAp5LkJCYW5kcy5tIDwtIEJCYW5kcyh5KSRtYXZnCmBgYAoKVGhlIGRyYXdpbmcgY29kZSBpcyBhcyBmb2xsb3dzLgpgYGB7cn0KaGMgPC0gaGlnaGNoYXJ0KHR5cGU9InN0b2NrIikgJT4lIAogIGhjX3RpdGxlKHRleHQ9IkNoYXJ0aW5nIEV4Y2hhbmdlIFJhdGVzIikgJT4lIAogIGhjX3N1YnRpdGxlKHRleHQgPSAiRGF0YSBleHRyYWN0ZWQgdXNpbmcgcXVhbnRtb2QgcGFja2FnZSIpICU+JSAKICBoY195QXhpc19tdWx0aXBsZXMoCiAgICBsaXN0KHRvcCA9ICIwJSIsIGhlaWdodCA9ICI1MCUiLCBvZmZzZXQ9MCwgb3Bwb3NpdGU9VFJVRSksCiAgICBsaXN0KHRvcCA9ICI1MCUiLCBoZWlnaHQgPSAiNTAlIiwgb2Zmc2V0PTAsIG9wcG9zaXRlPVRSVUUpCiAgKSU+JQogIGhjX2FkZF9zZXJpZXMoeCwgaWQgPSAiYXVkanB5IixuYW1lID0iYXVkanB5IiwgeUF4aXM9MCwgY29sb3I9ImJsdWUiLCBsaW5lV2lkdGg9MS41KSAlPiUKICBoY19hZGRfc2VyaWVzKHguQkJhbmRzLmxsLCBpZCA9ICJhdWRqcHkubGwiLCBuYW1lPSJhdWRqcHkgTG93ZXIgQkJhbmRzIix5QXhpcz0wLAogICAgICAgICAgICAgICAgY29sb3I9ImJsYWNrIixkYXNoU3R5bGU9J3Nob3J0ZGFzaCcsIGxpbmVXaWR0aD0xKSAlPiUKICBoY19hZGRfc2VyaWVzKHguQkJhbmRzLnVsLCBpZCA9ICJhdWRqcHkudWwiLCBuYW1lPSJhdWRqcHkgVXBwZXIgQkJhbmRzIix5QXhpcz0wLAogICAgICAgICAgICAgICAgY29sb3I9ImJsYWNrIixsaW5lV2lkdGg9MSkgJT4lCiAgaGNfYWRkX3Nlcmllcyh4LkJCYW5kcy5tLCBpZCA9ICJhdWRqcHkubSIsbmFtZT0iYXVkanB5IEJCYW5kcyBNQSIsIHlBeGlzPTAsCiAgICAgICAgICAgICAgICBjb2xvcj0icmVkIixsaW5lV2lkdGg9MSkgJT4lCiAgaGNfYWRkX3Nlcmllcyh5LCBpZCA9ICJnYnB1c2QiLG5hbWU9ImdicHVzZCIseUF4aXM9MSwgY29sb3I9ImdyZWVuIiwgbGluZVdpZHRoPTEuNSkgJT4lCiAgaGNfYWRkX3Nlcmllcyh5LkJCYW5kcy5sbCwgaWQgPSAiZ2JwdXNkLmxsIixuYW1lPSJnYnB1c2QgTG93ZXIgQkJhbmRzIiwgeUF4aXM9MSwKICAgICAgICAgICAgICAgIGNvbG9yPSJibGFjayIsZGFzaFN0eWxlPSdzaG9ydGRhc2gnLGxpbmVXaWR0aD0xKSAlPiUKICBoY19hZGRfc2VyaWVzKHkuQkJhbmRzLnVsLCBpZCA9ICJnYnB1c2QudWwiLG5hbWU9ImdicHVzZCBVcHBlciBCQmFuZHMiLCB5QXhpcz0xLAogICAgICAgICAgICAgICAgY29sb3I9ImJsYWNrIixsaW5lV2lkdGg9MSkgJT4lCiAgaGNfYWRkX3Nlcmllcyh5LkJCYW5kcy5tLCBpZCA9ICJnYnB1c2QubSIsbmFtZT0iZ2JwdXNkIEJCYW5kcyBNQSIsIHlBeGlzPTEsCiAgICAgICAgICAgICAgICBjb2xvcj0icmVkIixsaW5lV2lkdGg9MSkgJT4lCiAgaGNfYWRkX3RoZW1lKGhjX3RoZW1lXzUzOCgpKQoKaGMKYGBgCgoKIyMgUTQKCkxvYWQgbGlicmFyaWVzIGFuZCBjaGVjayB0aGUgcmF3IGRhdGEuIEFuZCBtYWtlIGZmZGYgYWZ0ZXIgY29udmVydGluZyBjaGFyYWN0ZXIgY29sdW1ucyB0byBmYWN0b3IgY29sdW1ucyBpbiBvcmlnaW5hbCBkZi4KYGBge3J9CmxpYnJhcnkobnljZmxpZ2h0czEzKQpsaWJyYXJ5KGZmYmFzZSkKbGlicmFyeShmZmJhc2UyKQpsaWJyYXJ5KGJpZ2xtKQpsaWJyYXJ5KHBST0MpCmxpYnJhcnkoY2hyb24pCgp0bXAgPC0gZmxpZ2h0cwp0bXAkY2FycmllciA8LSBhcy5mYWN0b3IodG1wJGNhcnJpZXIpCnRtcCR0YWlsbnVtIDwtIGFzLmZhY3Rvcih0bXAkdGFpbG51bSkKdG1wJG9yaWdpbiA8LSBhcy5mYWN0b3IodG1wJG9yaWdpbikKdG1wJGRlc3QgPC0gYXMuZmFjdG9yKHRtcCRkZXN0KQoKZmxpZ2h0ZmYgPC0gYXMuZmZkZih0bXApCmBgYAoKTmV4dCBJIG1ha2UgbmV3IGNvbHVtbnMgYXMgZm9sbG93cwpgYGB7cn0KZmxpZ2h0ZmYkRGVsYXkgPC0gZmZpZmVsc2UoZmxpZ2h0ZmYkZGVwX2RlbGF5ID4gMCB8IGZsaWdodGZmJGRlcF9kZWxheSA9PSAwICwgMSwwKQpmbGlnaHRmZiREZXBIb3VyIDwtIGZsaWdodGZmJGhvdXIKZmxpZ2h0ZmYkQ2FyIDwtIGZmaWZlbHNlKGZsaWdodGZmJGNhcnJpZXIgJWluJSBhcy5mYWN0b3IoYygiREwiLCJVUyIsIkRIIiwiVUEiKSksIDEsIDApCmZsaWdodGZmJE5pZ2h0IDwtIGZmaWZlbHNlKGZsaWdodGZmJGhvdXIgPiAxOCB8IGZsaWdodGZmJGhvdXIgPCA2LCAxLCAwKQpmbGlnaHRmZiRXZWVrZW5kIDwtIGZmaWZlbHNlKGRheS5vZi53ZWVrKG1vbnRoPWZsaWdodGZmJG1vbnRoLCBkYXk9ZmxpZ2h0ZmYkZGF5LCB5ZWFyPWZsaWdodGZmJHllYXIpID09IDYsIDEsIDApCmBgYAoKSSBleGNsdWRlIHRoZSByb3dzIHdob3NlIERlbGF5IHZhbHVlcyBhcmUgTkEgYW5kIHJlbmFtZSBpdCB0byBsb2dpdGZmLgpBbmQgdGhlbiBJIHNwbGl0IHRoZSBkYXRhc2V0IGludG8gdHJhaW4gc2V0IGFuZCB0ZXN0IHNldC4KYGBge3J9CmxvZ2l0ZmYgPC0gZmxpZ2h0ZmZbIWlzLm5hKGZsaWdodGZmJERlbGF5KSxdCgppbmR4IDwtIGZmKDE6bnJvdyhsb2dpdGZmKSkKcCA8LSAwLjcKdHJhaW5JbmR4IDwtIGZmKGluZHhbMTp0cnVuYyhsZW5ndGgoaW5keCkqcCldKQp0cmFpbnNldCA8LSBsb2dpdGZmW3RyYWluSW5keCxdCnRlc3RJbmR4IDwtIGZmKGluZHhbKHRydW5jKGxlbmd0aChpbmR4KSpwKSsxKTpsZW5ndGgoaW5keCldKQp0ZXN0c2V0IDwtIGxvZ2l0ZmZbdGVzdEluZHgsXQpgYGAKCkxvZ2lzdGljIHJlZ3Jlc3Npb24gCmBgYHtyfQpmaXQgPC0gYmlnZ2xtLmZmZGYoRGVsYXl+RGVwSG91citDYXIrTmlnaHQrV2Vla2VuZCwgZGF0YSA9IHRyYWluc2V0LCBmYW1pbHk9Ymlub21pYWwoKSwgc2FuZHdpY2g9VFJVRSkKc3VtbWFyeShmaXQpCmBgYAoKcHJlZGljdCBhbmQgbWFrZSBjb25mdXNpb25tYXRyaXggaW4gdHJhaW5fc2V0CmBgYHtyfQp0cmFpbl9wcmVkIDwtIHByZWRpY3QoZml0LCBuZXdkYXRhID0gdHJhaW5zZXQsIHR5cGU9InJlc3BvbnNlIikKdHJhaW5fcHJlZCA8LSBpZmVsc2UodHJhaW5fcHJlZD4wLjUsIDEsMCkKdHJhaW5fY29uZnVzaW9uIDwtIHRhYmxlKGFzLmludGVnZXIoYXMuZGF0YS5mcmFtZSh0cmFpbnNldCkkRGVsYXkpLCBhcy5pbnRlZ2VyKHRyYWluX3ByZWQpKQp0cmFpbl9jb25mdXNpb24gPC0gYWRkbWFyZ2lucyh0cmFpbl9jb25mdXNpb24pCnRyYWluX2NvbmZ1c2lvbgpgYGAKCnByZWRpY3QgYW5kIG1ha2UgY29uZnVzaW9ubWF0cml4IGluIHRlc3Rfc2V0CmBgYHtyfQp0ZXN0X3ByZWQgPC0gcHJlZGljdChmaXQsIG5ld2RhdGEgPSB0ZXN0c2V0LCB0eXBlPSJyZXNwb25zZSIpCnRlc3RfcHJlZCA8LSBpZmVsc2UodGVzdF9wcmVkPjAuNSwgMSwwKQp0ZXN0X2NvbmZ1c2lvbiA8LSB0YWJsZShhcy5pbnRlZ2VyKGFzLmRhdGEuZnJhbWUodGVzdHNldCkkRGVsYXkpLCBhcy5pbnRlZ2VyKHRlc3RfcHJlZCkpCnRlc3RfY29uZnVzaW9uIDwtIGFkZG1hcmdpbnModGVzdF9jb25mdXNpb24pCnRlc3RfY29uZnVzaW9uCmBgYAoKRHJhdyBST0MgY3VydmUKYGBge3J9CnRlc3RfcHJlZCA8LSBwcmVkaWN0KGZpdCwgbmV3ZGF0YSA9IHRlc3RzZXQsIHR5cGU9InJlc3BvbnNlIikKcm9jIDwtIHJvYyhhcy5pbnRlZ2VyKGFzLmRhdGEuZnJhbWUodGVzdHNldCkkRGVsYXkpLCBhcy5udW1lcmljKHRlc3RfcHJlZCkpCnBsb3Qocm9jKQpgYGAKCgojIyBRNQoKRmlyc3QgSSBsb2FkIHRoZSBkYXRhLiBBbmQgYmVmb3JlIHVzaW5nIHNwYXJrIEkgZGVsZXRlIHRoZSBpcnJlbGV2YW50IGNvbHVtbnMuCmBgYHtyfQpsaWJyYXJ5KHNwYXJrbHlyKQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KHJlYWRyKQpzYyA8LSBzcGFya19jb25uZWN0KG1hc3RlciA9ICJsb2NhbCIpCgojIOWFg+OBrmNzduOBq+WVj+mhjOOBjOOBguOCi+OBo+OBveOBhO+8nyDihpIgVW5zcGVjaWZpZWTjgYzmgJLjgonjgozjgabjgovjgaPjgb3jgYTjgIDihpLjgIBVbnNwZWNpZmllZOOCkjDjgavlpInjgYjjgovjgYvjgILihpLku4rluqbjga9QQVNTRU5HRVIgVkVISUNLReOBjOaAkuOCieOCjOOCi+OAggojIOimgeOBmeOCi+OBq1N0cmluZ+OBjOWFqOmDqOODgOODoeOBo+OBveOBhOOAguOBp+OCgnRpdGFuaWPjgafjga9TdHJpbmfjgoLkuIrmiYvjgY/muKHjgZvjgabjgovjgILjgarjgZzvvJ8KIyDkuozjgaTjga7kvovjgYvjgonnqbrnmb3jga/liKXjgavllY/poYzjgafjga/jgarjgYTjgZPjgajjgYzjgo/jgYvjgovjgIIKZGF0IDwtIHJlYWRfY3N2KCIvVXNlcnMvc3VzdS9EZXNrdG9wL0hvbmdcIEtvbmcvU2VtZXN0ZXIyL0JpZ19EYXRhL2Fzc2lnbm1lbnRfZGF0YS9hczEvTllQRF9Nb3Rvcl9WZWhpY2xlX0NvbGxpc2lvbnMuY3N2IikKZGF0IDwtIGRhdFssYygiQk9ST1VHSCIsIkxBVElUVURFIiwiTE9OR0lUVURFIiwiVU5JUVVFIEtFWSIpXQpueXBkX3RibCA8LSBjb3B5X3RvKHNjLCBkYXQsICJueXBkX3RibCIsb3ZlcndyaXRlID0gVFJVRSkKYGBgCgpSZW1vdmUgdGhlIG9ic2VydmF0aW9ucyBzYXRpc2Z5aW5nIHRoZSBjb25kaXRpb24KYGBge3J9Cm55cGRfdGJsIDwtIG55cGRfdGJsICU+JQogIGZpbHRlcihCT1JPVUdIIT0iIiwhaXMubmEoTEFUSVRVREUpLCFpcy5uYShMT05HSVRVREUpLExBVElUVURFIT0wLExPTkdJVFVERSE9MCkKbnlwZF90YmwKYGBgCgpTcGxpdCB0aGlzIGRhdGEgaW50byB0cmFpbnNldCBhbmQgdGVzdHNldC4KYGBge3J9CnBhcnRpdGlvbnMgPC0gbnlwZF90YmwgJT4lCiAgc2RmX3BhcnRpdGlvbih0cmFpbmluZyA9IDAuOSwgdGVzdCA9IDAuMSwgc2VlZCA9IDEyMykKdHJhaW4gPC0gcGFydGl0aW9uc1sxXSR0cmFpbmluZwp0ZXN0IDwtIHBhcnRpdGlvbnNbMl0kdGVzdApgYGAKClVzZSBEZWNpc2lvbiB0cmVlCmBgYHtyfQpkZWNpc2lvbl90cmVlIDwtIHRyYWluICU+JQogIG1sX2RlY2lzaW9uX3RyZWUocmVzcG9uc2U9IkJPUk9VR0giLCBmZWF0dXJlcyA9IGMoIkxBVElUVURFIiwiTE9OR0lUVURFIiksIG1heC5iaW5zID0gMjAwTCwgbWF4LmRlcHRoID0gMTBMLCBzZWVkPTEyM0wpICU+JQpgYGAKClByZWRpY3Rpb24KYGBge3J9CnByZWQgPC0gc2RmX3ByZWRpY3QoZGVjaXNpb25fdHJlZSwgdGVzdCkgJT4lCiAgY29sbGVjdAoKdGFibGUocHJlZCRCT1JPVUdILCBwcmVkJHByZWRpY3Rpb24pCmBgYAo=